package org.budo.netty.servlet.server.connector.netty;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;

import org.budo.netty.servlet.server.connector.AbstractConnector;
import org.budo.netty.servlet.server.connector.Connector;
import org.budo.netty.servlet.server.handler.BudoServletHandler;
import org.budo.netty.servlet.server.handler.servlet.AbstractBudoServletRequest;
import org.budo.netty.servlet.server.handler.servlet.BudoServletInputStream;
import org.budo.netty.servlet.server.handler.servlet.ServletResolved;
import org.budo.netty.servlet.util.StreamUtil;
import org.budo.netty.servlet.util.UrlUtil;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;

/**
 * @author li
 */
public class NettyRequest extends AbstractBudoServletRequest {
    private ChannelHandlerContext handlerContext;

    private FullHttpRequest nettyRequest;

    private ServletResolved servletResolved;

    private Map<String, String[]> parameterMap;

    public NettyRequest(BudoServletHandler servletHandler, ChannelHandlerContext handlerContext, FullHttpRequest nettyRequest) {
        this.setBudoServletHandler(servletHandler);

        this.handlerContext = handlerContext;
        this.nettyRequest = nettyRequest;
    }

    @Override
    public void setServletResolved(ServletResolved servletResolved) {
        this.servletResolved = servletResolved;
    }

    @Override
    public String getServletPath() {
        if (null == this.servletResolved || null == this.servletResolved.getServletPath()) {
            return "";
        }

        return this.servletResolved.getServletPath();
    }

    @Override
    public String getPathInfo() {
        if (null == this.servletResolved || null == this.servletResolved.getServletPath()) {
            return this.getRequestURI();
        }

        return this.servletResolved.getPathInfo();
    }

    /**
     * requestURI 不包含 queryString
     */
    @Override
    public String getRequestURI() {
        String uri = this.nettyRequest.uri();
        int index = uri.indexOf('?');
        if (index == -1) {
            return uri;
        }

        return uri.substring(0, index);
    }

    @Override
    public String getQueryString() {
        String uri = this.nettyRequest.uri();
        int index = uri.indexOf('?');
        if (index == -1) {
            return null;
        }

        return uri.substring(index + 1);
    }

    @Override
    public String getHeader(String name) {
        return this.nettyRequest.headers().get(name);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteBuf byteBuf = this.nettyRequest.content();
        String requestBody = NettyUtil.byteBufToString(byteBuf);
        return new BudoServletInputStream(requestBody);
    }

    @Override
    public String getMethod() {
        return this.nettyRequest.method().name();
    }

    @Override
    public String getScheme() {
        return "http";
    }

    /**
     * @see org.eclipse.jetty.server.Request#extractContentParameters()
     */
    @Override
    public Map<String, String[]> getParameterMap() {
        if (null != this.parameterMap) {
            return this.parameterMap;
        }

        synchronized (this) {
            if (null != this.parameterMap) {
                return this.parameterMap;
            }

            this.parameterMap = new HashMap<String, String[]>();

            String queryString = this.getQueryString();
            Map<String, String[]> queryStringMap = UrlUtil.queryStringToMap(queryString);
            this.parameterMap.putAll(queryStringMap);

            Map<String, String[]> requestBodyParameters = this.requestBodyParameters();
            this.parameterMap.putAll(requestBodyParameters);

            return this.parameterMap;
        }
    }

    private Map<String, String[]> requestBodyParameters() {
        String method = this.getMethod();

        if (!method.equals("POST") && !method.equals("PUT")) {
            return new HashMap<String, String[]>();
        }

        String contentType = this.getContentType();
        if (null == contentType || !contentType.startsWith("application/x-www-form-urlencoded")) {
            return new HashMap<String, String[]>();
        }

        try {
            String requestBody = StreamUtil.toString(this.getInputStream());
            return UrlUtil.queryStringToMap(requestBody);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String getRemoteAddr() {
        SocketAddress remoteAddress = this.handlerContext.channel().remoteAddress();
        InetSocketAddress inetSocketAddress = (InetSocketAddress) remoteAddress;
        return inetSocketAddress.getAddress().getHostAddress();
    }

    @Override
    public int getRemotePort() {
        SocketAddress remoteAddress = this.handlerContext.channel().remoteAddress();
        InetSocketAddress inetSocketAddress = (InetSocketAddress) remoteAddress;
        return inetSocketAddress.getPort();
    }

    @Override
    public Locale getLocale() {
        return Locale.getDefault();
    }

    @Override
    public Cookie[] getCookies() {
        String headerValue = this.nettyRequest.headers().get(HttpHeaderNames.COOKIE);
        if (null == headerValue || headerValue.isEmpty()) {
            return new Cookie[0];
        }

        Set<io.netty.handler.codec.http.cookie.Cookie> nettyCookies = ServerCookieDecoder.STRICT.decode(headerValue);
        Cookie[] cookies = new Cookie[nettyCookies.size()];
        int i = 0;
        for (io.netty.handler.codec.http.cookie.Cookie nettyCookie : nettyCookies) {
            cookies[i] = new Cookie(nettyCookie.name(), nettyCookie.value());
            i++;
        }

        return cookies;
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return Collections.enumeration(this.nettyRequest.headers().names());
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        return Collections.enumeration(this.nettyRequest.headers().getAll(name));
    }

    @Override
    public String getProtocol() {
        return "HTTP/1.1";
    }

    @Override
    public int getServerPort() {
        Connector connector = this.getBudoServletHandler().getBudoServletServer().getConnector();
        return ((AbstractConnector) connector).getPort();
    }

    @Override
    public String getLocalName() {
        InetSocketAddress localAddress = (InetSocketAddress) this.handlerContext.channel().localAddress();
        return localAddress.getHostString();
    }

    @Override
    public String toString() {
        return super.toString() + ", requestURI=" + this.getRequestURI();
    }
}